<template>
    <div class="tnxel-edit-table-container">
        <el-table ref="table"
            class="tnxel-edit-table"
            :class="{'padding-none': !padding}"
            :data="data"
            :highlight-current-row="selectable"
            border
            @current-change="onSelectRow"
        >
            <slot></slot>
            <el-table-column label="排序" align="center" width="50px" v-if="sortable">
                <template #default="scope">
                    <el-button
                        type="text"
                        icon="el-icon-top"
                        :class="{'text-transparent': scope.$index === 0}"
                        @click="sortUp(scope.$index)"
                    />
                    <el-button
                        type="text"
                        icon="el-icon-bottom"
                        class="ms-0"
                        :class="{'text-transparent': scope.$index === data.length - 1}"
                        @click="sortDown(scope.$index)"
                    />
                </template>
            </el-table-column>
            <el-table-column label="移除" align="center" width="50px" v-if="removable">
                <template #default="scope">
                    <i class="el-icon-circle-close disabled" :title="scope.row.removeTip" v-if="scope.row.removeTip"/>
                    <el-button
                        type="text"
                        icon="el-icon-circle-close"
                        @click="toRemoveRow(scope.$index)"
                        v-else
                    />
                </template>
            </el-table-column>
        </el-table>
        <el-table class="tnxel-edit-table-footer" :data="[{}]" border :show-header="false" v-if="addable">
            <el-table-column align="center">
                <template #default>
                    <el-button type="text" icon="el-icon-plus" @click="toAddRow">{{ addText }}</el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>

<script>
export default {
    name: 'TnxelEditTable',
    props: {
        data: Array,
        addable: {
            type: Boolean,
            default: true,
        },
        removable: {
            type: Boolean,
            default: true,
        },
        addText: {
            type: String,
            default: '添加行',
        },
        newRow: {
            type: Function,
            default() {
                return () => {
                    return {};
                }
            }
        },
        selectable: Boolean, // 可否选择行
        padding: Boolean, // 单元格中是否有边距
        sortable: Boolean, // 可否调整行的顺序
        rules: [String, Object], // 加载字段校验规则的URL地址，或规则集对象
        rulesApp: { // 加载字段校验规则的应用名称
            type: String,
            default: () => window.tnx.componentDefaultApp, // 设置为方法以延时加载，确保更改的值生效
        },
        rulesLoaded: Function, // 规则集加载后的附加处理函数，仅在rule为字符串类型的URL地址时有效
    },
    data() {
        return {
            selectedRow: null,
            validationRules: {},
        }
    },
    watch: {
        data(newData, oldData) {
            if (!oldData) {
                this.$nextTick(() => {
                    this.initElements();
                });
            }
        },
        rules() {
            this.initRules();
        },
    },
    mounted() {
        if (this.selectable) {
            window.tnx.util.dom.replaceKeyEvent(document, () => {
                if (this.selectedRow) {
                    this.data.remove(record => record === this.selectedRow);
                }
            }, {key: 'Delete'});
        }
        this.$nextTick(() => {
            this.initElements();
            this.initRules();
        });
    },
    methods: {
        initElements() {
            this.focusRowFirstInput(0);
            // 为富文本输入框添加默认title
            let table = this.$refs.table.$el;
            let textareas = table.getElementsByTagName('textarea');
            for (let textarea of textareas) {
                if (!textarea.title) {
                    textarea.addEventListener('input', event => {
                        event.target.title = event.target.value;
                    });
                }
            }
        },
        initRules() {
            if (this.rules) {
                if (typeof this.rules === 'string') {
                    window.tnx.app.rpc.getMeta(this.rules, meta => {
                        if (this.rulesLoaded) {
                            this.rulesLoaded(meta.$rules);
                        } else {
                            this.$emit('rules-loaded', meta.$rules);
                        }
                        this.validationRules = meta.$rules;
                        this.bindRules();
                    }, this.rulesApp);
                } else {
                    this.validationRules = this.rules;
                    this.bindRules();
                }
            } else {
                this.validationRules = {};
                this.bindRules();
            }
        },
        loopColumns(consumer) {
            let columns = this.$slots.default;
            for (let column of columns) {
                let props = column.componentOptions.propsData;
                let fieldName = props.prop;
                let columnId = column.componentInstance.columnId;
                consumer(fieldName, columnId);
            }
        },
        loopValidatableElements(consumer) {
            this.loopColumns((fieldName, columnId) => {
                if (this.validationRules[fieldName]) {
                    let tds = this.$refs.table.$el.getElementsByClassName(columnId);
                    for (let td of tds) {
                        let elements = td.getElementsByTagName('input');
                        if (elements.length === 0) {
                            elements = td.getElementsByTagName('textarea');
                        }
                        for (let element of elements) {
                            consumer(fieldName, element);
                        }
                    }
                }
            });
        },
        bindRules() {
            this.loopValidatableElements((fieldName, element) => {
                element.addEventListener('blur', event => {
                    let element = event.target;
                    this.validateElement(fieldName, element);
                });
                element.addEventListener('focus', event => {
                    this.removeErrorIcon(event.target);
                });
            });
            this.loopColumns((fieldName, columnId) => {
                let rules = this.validationRules[fieldName];
                if (rules) {
                    for (let rule of rules) {
                        if (rule.required) {
                            let th = this.$refs.table.$el.querySelector('th.' + columnId);
                            if (th) {
                                th.classList.add('is-required');
                            }
                            break;
                        }
                    }
                }
            });
        },
        validateElement(fieldName, element) {
            this.removeErrorIcon(element);
            element.parentElement.classList.remove('is-error');

            let rules = {};
            rules[fieldName] = this.validationRules[fieldName];
            let model = {};
            model[fieldName] = element.value;
            let successful = true;
            window.tnx.app.validator.validate(rules, model).catch(errors => {
                if (errors) {
                    successful = false;
                    let message = '';
                    for (let error of errors) {
                        message += error.message + '\n';
                    }
                    message = message.trim();
                    element.parentElement.classList.add('is-error');
                    let icon = document.createElement('i');
                    icon.classList.add('error-icon');
                    icon.classList.add('el-icon-warning');
                    icon.classList.add('text-danger');
                    icon.title = message;
                    icon.style.top = (element.parentElement.offsetHeight - 16) / 2 + 'px';
                    element.parentElement.appendChild(icon);
                }
            });
            return successful;
        },
        validateTable() {
            let successful = true;
            this.loopValidatableElements((fieldName, element) => {
                let validated = this.validateElement(fieldName, element);
                successful = validated && successful;
            });
            return successful;
        },
        removeErrorIcon(inputElement) {
            let sibling = inputElement.nextSibling;
            while (sibling) {
                if (sibling.classList && sibling.classList.contains('error-icon')) {
                    sibling.remove();
                }
                sibling = sibling.nextSibling;
            }
        },
        onSelectRow(row) {
            if (this.selectable) {
                this.selectedRow = row;
            }
        },
        focusRowFirstInput(rowIndex) {
            let table = this.$refs.table.$el;
            let rows = table.getElementsByClassName('el-table__row');
            if (rows.length) {
                let row = rows[rowIndex];
                if (row) {
                    let cells = row.getElementsByClassName('cell');
                    for (let cell of cells) {
                        let input = cell.querySelector('input:first-child');
                        if (input) {
                            input.focus();
                            return true;
                        } else {
                            let textarea = cell.querySelector('textarea:first-child');
                            if (textarea) {
                                textarea.focus();
                                return true;
                            }
                        }
                    }
                }
            }
            return false;
        },
        toAddRow() {
            let row = this.newRow();
            this.data.push(row);
            this.$nextTick(() => {
                this.focusRowFirstInput(this.data.length - 1);
            });
        },
        toRemoveRow(index) {
            let row = this.data[index];
            this.data.splice(index, 1);
            this.$emit('removed', row, index);
            this.$nextTick(() => {
                if (!this.focusRowFirstInput(index)) {
                    this.focusRowFirstInput(index - 1);
                }
            });
        },
        sortDown(index) {
            if (0 <= index && index < this.data.length - 1) {
                let deleted = this.data.splice(index, 1);
                this.data.splice(index + 1, 0, deleted[0]);
            }
        },
        sortUp(index) {
            if (0 < index && index < this.data.length) {
                let deleted = this.data.splice(index, 1);
                this.data.splice(index - 1, 0, deleted[0]);
            }
        },
    }
}
</script>

<style>
.tnxel-edit-table-container .el-table.padding-none .el-table__cell,
.tnxel-edit-table-container .el-table.padding-none td .cell,
.tnxel-edit-table-container .el-table--border.padding-none td.el-table__cell:first-child .cell {
    padding: 0;
}

.tnxel-edit-table-container .el-table.padding-none .el-input__inner {
    border-radius: 0;
    border-color: transparent;
    padding-left: 10px;
    padding-right: 10px;
}

.tnxel-edit-table-container .el-table.padding-none .el-textarea__inner {
    border-radius: 0;
    border-color: transparent;
    padding: 9px 10px 0;
    height: 40px;
}

.tnxel-edit-table-container .tnxel-edit-table .is-center .el-input__inner {
    text-align: center;
}

.tnxel-edit-table-container .tnxel-edit-table .is-right .el-input__inner {
    text-align: right;
}

.tnxel-edit-table-container .el-table.padding-none .el-input__inner:focus,
.tnxel-edit-table-container .el-table.padding-none .el-textarea__inner:focus {
    border-color: var(--el-color-primary);
}

.tnxel-edit-table-container .el-table .is-error .el-input__inner,
.tnxel-edit-table-container .el-table .is-error .el-textarea__inner {
    border-color: var(--el-color-danger);
}

.tnxel-edit-table-container .el-table th.is-required:not(.is-no-asterisk) .cell:after {
    content: "*";
    color: var(--el-color-danger);
    margin-left: 4px;
}

.tnxel-edit-table-container .el-table .is-error .error-icon {
    position: absolute;
    top: 12px;
    right: 10px;
    font-size: 16px;
}

.tnxel-edit-table-container .el-table .el-input__inner:focus + .error-icon,
.tnxel-edit-table-container .el-table .el-textarea__inner:focus + .error-icon {
    display: none;
}

.tnxel-edit-table-container .el-table .el-button.text-transparent {
    cursor: default;
}

.tnxel-edit-table-container .el-table .disabled {
    cursor: not-allowed;
}

.tnxel-edit-table-container .tnxel-edit-table-footer {
    border-top: none;
}
</style>
